#pragma once

#include <nanobdd/Node.h>
#include <shared_mutex>
#include <vector>

namespace nanobdd {

struct CacheEntry {
  Node* node;
  Node* left;
  Node* right;
  uint32_t op;
  std::unique_ptr<std::shared_mutex> mutex;
};

class Cache {
 public:
  Cache(size_t size) : size_(size), cache_(size) {
    for (auto& e : cache_) {
      e.mutex = std::make_unique<std::shared_mutex>();
    }
  }

  Node*
  lookup(uint32_t hash, Node* left, Node* right, uint32_t op) {
    // const std::lock_guard<std::mutex> lock(mutex_);
    // copy the entry for concurrency
    auto& entry = cache_.at(hash);
    std::shared_lock<std::shared_mutex> lock(*entry.mutex);
    if (entry.left == left && entry.right == right && entry.op == op) {
      return entry.node;
    }

    return nullptr;
  }

  void
  insert(uint32_t hash, Node* node, Node* left, Node* right, uint32_t op) {
    auto& entry = cache_.at(hash);
    const std::lock_guard<std::shared_mutex> lock(*entry.mutex);
    entry.node = node;
    entry.left = left;
    entry.right = right;
    entry.op = op;
  }

 private:
  size_t size_;
  std::vector<CacheEntry> cache_;
};

} // namespace nanobdd